﻿package com.godpaper.controllers.mxml.ds
{
	import de.polygonal.ds.DLinkedList;
	import de.polygonal.ds.DListIterator;
	import de.polygonal.ds.DListNode;
	
	import flash.display.*;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.geom.Point;
	
	import mx.core.UIComponent;
	
	public class LinkedListBuilder extends UIComponent
	{
		public static var SW:Number         = 550;
		public static var SH:Number         = 200;
		public static var BLUE:uint         = 0X03A3F0;
		public static var BLUE_LIGHT:uint   = 0X81D1F7;
		public static var PURPLE:uint       = 0XF40095;
		public static var PURPLE_LIGHT:uint = 0XF97FCA;
		public static var GREY:uint         = 0XCCCCCC;
		[Bindable] public var START_NODES:int;
		[Bindable] public var MAX_NODES:int;
		public static var RADIUS_OFFSET:int = 5;
		
		public var dll:DLinkedList;
		
		public var canvas0:UIComponent;
		public var canvas1:UIComponent;
		
		public var inserting:Boolean;
		
		public function LinkedListBuilder(startNodes:Number,maxNodes:Number,maxNodeRadius:Number)
		{
			START_NODES = startNodes;
			MAX_NODES = maxNodes;
			
				
			addEventListener(Event.ADDED_TO_STAGE,function(event:Event):void
			{
				stage.scaleMode = StageScaleMode.NO_SCALE;
				stage.align = StageAlign.TOP_LEFT;
				stage.addEventListener(KeyboardEvent.KEY_DOWN, keyHander);
			});
			
			
			dll = new DLinkedList();
			
			for (var i:int = 0; i < START_NODES; i++)
			{
				var node:Node = new Node();
				addChild(node);
				
				dll.append(node);
			}
			
			addChild(canvas0 = new UIComponent());
			addChild(canvas1 = new UIComponent());
			
			align();
			drawLinks();
			
			inserting = false;
		}
		
		public function keyHander(e:KeyboardEvent):void
		{
			//I - insert node
			if (e.keyCode == 73)
			{
				if (Node.NUM_NODES < MAX_NODES && !inserting)
				{
					var node:Node = new Node();
					addChild(node);
					node.insert();
					inserting = true;
					
					node.radius = Node.NODE_RADIUS / 2;
					node.draw();
				}
			}
		}
		
		public function onNodeDrag(node:Node):void
		{
			if (node.inserted)
			{
				var itr:DListIterator;
				
				//remove node from list
				var walker:DListNode = dll.head;
				while (walker)
				{
					if (walker.data == node)
					{
						dll.remove(new DListIterator(dll, walker));
						break;
					}
					walker = walker.next;
				}
				
				var headNode:Node = dll.head.data;
				
				//and insert at new position while dragging
				walker = dll.head;
				while (walker && (node.x > walker.data.x))
				{
					walker = walker.next;
				}
				
				if (walker == null)
					dll.append(node);
				else
				if (walker == dll.head)
					dll.prepend(node);
				else
					dll.insertBefore(new DListIterator(dll, walker), node);
				
			}
			
			drawLinks(node);
			
			if (!node.inserted)
				drawSelectedLinks(node);
		}
		
		public function onNodeRelease(node:Node):void
		{
			if (node.inserted)
			{
				node.inserted = false;
				inserting = false;
				align();
				drawLinks();
			}
			else
				removeNode(node);
		}
		
		public function removeNode(node:Node):void
		{
			var itr:DListIterator = dll.getListIterator();
			while (itr.valid())
			{
				if (itr.data == node)
				{
					itr.remove();
					break;
				}
				itr.next();
			}
			
			node.deconstruct();
			align();
			drawLinks();
			canvas1.graphics.clear();
		}
		
		public function align():void
		{
			var xEdgeOffset:Number =  Node.NODE_RADIUS + 40
			var xSpacing:Number = (SW - (2 * xEdgeOffset)) / (Node.NUM_NODES - 1);
			
			var r:Number = Math.min(Node.NODE_RADIUS * (START_NODES) / Node.NUM_NODES, Node.NODE_RADIUS);
			
			var i:int = 0;
			for (var itr:DListIterator = dll.getListIterator(); itr.valid(); itr.forth())
			{
				var node:Node = itr.data as Node;
				node.setPos(xEdgeOffset + (i++ * xSpacing), SH / 2);
				
				node.radius = r;
				node.draw();
			}
		}
		
		public function drawSelectedLinks(currNode:Node = null):void
		{
			var g:Graphics = canvas1.graphics;
			g.clear();
			g.lineStyle(0);
			
			if (currNode.getYOffset() < .5) return;
			
			var offsetAngle:Number = 15 * Math.PI / 180;
			
			var dlnode:DListNode = null;
			for (var itr:DListIterator = dll.getListIterator(); itr.valid(); itr.forth())
			{
				if (itr.node.data == currNode)
				{
					dlnode = itr.node;
					break;
				}
			}
			
			var prevNode:Node = null;
			var nextNode:Node = null;
			
			var anchorPrev_t:Point, anchorPrev_b:Point;
			var anchorCurr_tr:Point, anchorCurr_br:Point, anchorCurr_tl:Point, anchorCurr_bl:Point, anchorNext_t:Point, anchorNext_b:Point;
			
			var dir0:Point = new Point(Math.cos( offsetAngle), Math.sin( offsetAngle));
			var dir1:Point = new Point(Math.cos(-offsetAngle), Math.sin(-offsetAngle));
			
			var r:Number, rOffset:Number = RADIUS_OFFSET;
			
			if (!dlnode.prev)
			{
				nextNode = dlnode.next.data;
				
				r = currNode.radius + rOffset;
				anchorCurr_tr = new Point(currNode.x + dir1.x * r, currNode.y + dir1.y * r);
				anchorCurr_br = new Point(currNode.x + dir0.x * r, currNode.y + dir0.y * r);
				
				r = nextNode.radius + rOffset;
				anchorNext_t = new Point(nextNode.x - dir0.x * r, nextNode.y - dir0.y * r);
				anchorNext_b = new Point(nextNode.x - dir1.x * r, nextNode.y - dir1.y * r);
				
				drawLink(g, anchorCurr_tr, anchorNext_t, GREY)
				drawLink(g, anchorNext_b, anchorCurr_br, GREY);
			}
			else
			if (!dlnode.next)
			{
				prevNode = dlnode.prev.data;
				
				r = prevNode.radius + rOffset;
				anchorPrev_t = new Point(prevNode.x + dir0.x * r, prevNode.y + dir0.y * r);
				anchorPrev_b = new Point(prevNode.x + dir1.x * r, prevNode.y + dir1.y * r);
				
				r = currNode.radius + rOffset;
				anchorCurr_tl = new Point(currNode.x - dir1.x * r, currNode.y - dir1.y * r);
				anchorCurr_bl = new Point(currNode.x - dir0.x * r, currNode.y - dir0.y * r);
				
				drawLink(g, anchorCurr_tl, anchorPrev_t, GREY)
				drawLink(g, anchorPrev_b, anchorCurr_bl, GREY);
			}
			else
			{
				nextNode = dlnode.next.data;
				prevNode = dlnode.prev.data;
				
				r = prevNode.radius + rOffset;
				anchorPrev_t = new Point(prevNode.x + dir0.x * r, prevNode.y + dir0.y * r);
				anchorPrev_b = new Point(prevNode.x + dir1.x * r, prevNode.y + dir1.y * r);
				
				r = currNode.radius + rOffset;
				anchorCurr_tl = new Point(currNode.x - dir1.x * r, currNode.y - dir1.y * r);
				anchorCurr_bl = new Point(currNode.x - dir0.x * r, currNode.y - dir0.y * r);
				anchorCurr_tr = new Point(currNode.x + dir1.x * r, currNode.y + dir1.y * r);
				anchorCurr_br = new Point(currNode.x + dir0.x * r, currNode.y + dir0.y * r);
				
				r = nextNode.radius + rOffset;
				anchorNext_t = new Point(nextNode.x - dir0.x * r, nextNode.y - dir0.y * r);
				anchorNext_b = new Point(nextNode.x - dir1.x * r, nextNode.y - dir1.y * r);
				
				//top
				var anchorCurr_ct:Point = new Point(currNode.x, currNode.y - r - 10);
				var anchorCurr_cb:Point = new Point(currNode.x, currNode.y - r);
				
				var alpha:Number = 1;
				
				if (currNode.getYOffset() >= .5) alpha = .35;
				
				drawLink(g, anchorCurr_tr, anchorNext_t, GREY);
				drawLink(g, anchorCurr_tl, anchorPrev_t, GREY);
				
				if (currNode.getYOffset() >= .5)
				{
					drawLink(g, anchorNext_b, anchorCurr_br, BLUE_LIGHT);
					drawLink(g, anchorPrev_b, anchorCurr_bl, PURPLE_LIGHT)
				}
				else
				{
					drawLink(g, anchorNext_b, anchorCurr_br, BLUE);
					drawLink(g, anchorPrev_b, anchorCurr_bl, PURPLE);
				}
				
				if (currNode.getYOffset() >= .5)
				{
					drawLink(g, anchorNext_b, anchorPrev_t, BLUE, 20);
					drawLink(g, anchorPrev_b, anchorNext_t, PURPLE, 20);
				}
			}
		}
		
		public function drawLinks(exclude:Node = null):void
		{
			var g:Graphics = canvas0.graphics;
			
			g.clear();
			g.lineStyle(0);
			
			var dlnode:DListNode;
			
			var offsetAngle:Number = 15 * Math.PI / 180;
			
			for (var itr:DListIterator = dll.getListIterator(); itr.valid(); itr.forth())
			{
				dlnode = itr.node;
				
				var currNode:Node = dlnode.data;
				
				if (currNode == exclude && exclude.getYOffset() >= .5) continue;
				
				if (!dlnode.next) break;
				
				var nextNode:Node = dlnode.next.data;
				
				if (nextNode == exclude && exclude.getYOffset() >= .5) continue;
				
				var dir0:Point = new Point(Math.cos( offsetAngle), Math.sin( offsetAngle));
				var dir1:Point = new Point(Math.cos(-offsetAngle), Math.sin(-offsetAngle));
				
				var r:Number = currNode.radius + RADIUS_OFFSET;
				
				//curr----->next
				var anchorCurr_t:Point = new Point(currNode.x + dir1.x * r, currNode.y + dir1.y * r);
				var anchorCurr_b:Point = new Point(currNode.x + dir0.x * r, currNode.y + dir0.y * r);
				
				r = nextNode.radius + RADIUS_OFFSET;
				
				//curr<----next
				var anchorNext_t:Point = new Point(nextNode.x - dir0.x * r, nextNode.y - dir0.y * r);
				var anchorNext_b:Point = new Point(nextNode.x - dir1.x * r, nextNode.y - dir1.y * r);
				
				drawLink(g, anchorCurr_t, anchorNext_t, PURPLE, 1);
				drawLink(g, anchorNext_b, anchorCurr_b, BLUE, 1);
			}
		}
		
		public function drawLink(g:Graphics, a:Point, b:Point, clr:uint, offset:Number = 0):void
		{
			var dir:Point = new Point(b.x - a.x, b.y - a.y);
			dir.normalize(1);
			var dist:Number = Point.distance(a, b) - 2 * offset;
			g.lineStyle(2, clr, 1);
			
			drawArrow(g, a.x + dir.x * offset, a.y + dir.y * offset, dir.x, dir.y, dist, 4);
		}
		
		public function drawLine(g:Graphics, a:Point, b:Point, clr:uint, offset:Number = 0):void
		{
			var dir:Point = new Point(b.x - a.x, b.y - a.y);
			dir.normalize(1);
			var dist:Number = Point.distance(a, b);
			g.lineStyle(2, clr, 1);
			
			g.moveTo(a.x + dir.x * offset, a.y + dir.y * offset);
			g.lineTo(b.x - dir.x * offset, b.y - dir.y * offset);
		}
		
		public function drawArrow(g:Graphics, x:Number, y:Number, xdir:Number, ydir:Number, len:Number, size:Number):void
		{
			var t:Number = 1 / Math.sqrt(xdir * xdir + ydir * ydir);
			
			var ex:Number = (xdir * t * len) + x;
			var ey:Number = (ydir * t * len) + y;
			
			g.moveTo(x, y);
			g.lineTo(ex, ey);
			
			var dx:Number = (ex - x);
			var dy:Number = (ey - y);
			
			var l:Number = Math.sqrt((dx * dx) + (dy * dy));
			if (l <= 0)	return;
			
			dx /= l;
			dy /= l;
			var nx:Number = -dy;
			var ny:Number =  dx;
			g.moveTo(ex, ey);
			g.lineTo((ex - (size * dx)) - (size * nx), (ey - (size * dy)) - (size * ny));
			g.moveTo(ex, ey);
			g.lineTo((ex - (size * dx)) + (size * nx), (ey - (size * dy)) + (size * ny));
		}
	}
}